package com.hh.recommend;

import cn.hutool.core.date.DateUtil;
import com.hh.entity.domain.UserBehavior;
import com.hh.mapper.UserBehaviorMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * com.hh.recommend
 *
 * @author
 * @version 0.0.1
 * @date 2025/7/25
 **/
@Slf4j
@Service
public class ItemCFRecommender {

    @Resource
    private UserBehaviorMapper behaviorMapper;

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Async("taskExecutor")
//    @Scheduled(cron = "5 * * * * ?")
    public void calculateItemSimilarities() {
        log.info("开始计算物品相似度");
        // 1. 获取用户行为数据（最近90天）
        Date cutoff = DateUtil.offsetDay(new Date(), -90);
        List<UserBehavior> behaviors = behaviorMapper.selectAfterDate(cutoff);

        // 2. 构建用户-物品评分矩阵
        Map<Long, Map<Long, Float>> userItemMatrix = new HashMap<>();
        behaviors.forEach(behavior -> {
            float weight = getBehaviorWeight(behavior);
            userItemMatrix.computeIfAbsent(behavior.getUserId(), k -> new HashMap<>())
                    .merge(behavior.getItemId(), weight, Float::sum);
        });

        // 3. 计算物品共现矩阵
        Map<Long, Map<Long, Float>> cooCurrencyMatrix = new HashMap<>();
        Map<Long, Float> itemPopularity = new HashMap<>();

        userItemMatrix.values().forEach(itemMap -> {
            List<Long> items = new ArrayList<>(itemMap.keySet());
            for (int i = 0; i < items.size(); i++) {
                Long item1 = items.get(i);
                itemPopularity.merge(item1, 1f, Float::sum);

                for (int j = i + 1; j < items.size(); j++) {
                    Long item2 = items.get(j);
                    cooCurrencyMatrix.computeIfAbsent(item1, k -> new HashMap<>())
                            .merge(item2, 1f, Float::sum);
                    cooCurrencyMatrix.computeIfAbsent(item2, k -> new HashMap<>())
                            .merge(item1, 1f, Float::sum);
                }
            }
        });

        // 4. 计算余弦相似度
        Map<Long, Map<Long, Float>> similarityMatrix = new HashMap<>();
        cooCurrencyMatrix.forEach((item1, relatedItems) -> {
            Map<Long, Float> similarities = new HashMap<>();
            relatedItems.forEach((item2, coCount) -> {
                float sim = coCount /
                        (float) Math.sqrt(itemPopularity.get(item1) * itemPopularity.get(item2));
                similarities.put(item2, sim);
            });
            similarityMatrix.put(item1, similarities);
        });

        // 5. 存储到Redis（使用ZSET存储每个物品的Top50相似物品）
        similarityMatrix.forEach((itemId, similarities) -> {
            String key = "rec:item_sim:" + itemId;
            redisTemplate.delete(key);

            Set<ZSetOperations.TypedTuple<String>> tuples = new HashSet<>();
            similarities.forEach((simItemId, sim) -> {
                tuples.add(new DefaultTypedTuple<>(
                        simItemId.toString(),
                        (double) sim
                ));
            });

            // 保留Top50相似物品
            List<ZSetOperations.TypedTuple<String>> sortedTuples = new ArrayList<>(tuples);
            sortedTuples.sort((t1, t2) -> t2.getScore().compareTo(t1.getScore()));
            int limit = Math.min(50, sortedTuples.size());
            // 将List转换为Set以匹配add方法的参数类型
            Set<ZSetOperations.TypedTuple<String>> limitedTuples = new HashSet<>(sortedTuples.subList(0, limit));
            redisTemplate.opsForZSet().add(key, limitedTuples);
        });
    }

    private float getBehaviorWeight(UserBehavior behavior) {
        return switch (behavior.getBehaviorType()) {
            case 1 -> 1.0f; // 浏览
            case 2 -> 3.0f; // 收藏
            case 3 -> 5.0f; // 购买
            case 4 -> behavior.getBehaviorValue(); // 评分
            default -> 0.5f;
        };
    }

    // 获取实时推荐
    public List<Long> recommendItems(Long userId, int topN) {
        // 1. 获取用户最近交互的20个物品
        List<Long> recentItems = behaviorMapper.selectRecentItems(userId, 5);

        // 2. 从Redis获取相似物品
        Map<Long, Float> candidateScores = new HashMap<>();
        for (Long itemId : recentItems) {
            String key = "rec:item_sim:" + itemId;
            Set<ZSetOperations.TypedTuple<String>> tuples =
                    redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, -1);

            if (tuples != null) {
                tuples.forEach(tuple -> {
                    Long candidate = Long.parseLong(tuple.getValue());
                    float score = tuple.getScore().floatValue();
                    candidateScores.merge(candidate, score, Float::sum);
                });
            }
        }

        // 3. 过滤已交互物品
        Set<Long> interactedItems = behaviorMapper.selectInteractedItems(userId);
        candidateScores.keySet().removeAll(interactedItems);

        // 4. 返回TopN推荐
        return candidateScores.entrySet().stream()
                .sorted(Map.Entry.<Long, Float>comparingByValue().reversed())
                .limit(topN)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
    }
}